1 : <?php
2 : /**
3 : * This file is part of the {@link http://aksw.org/Projects/Erfurt Erfurt} project.
4 : *
5 : * @copyright Copyright (c) 2012, {@link http://aksw.org AKSW}
6 : * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
7 : */
8 :
9 : /**
10 : * Parses a SPARQL Query string and returns a Erfurt_Sparql_Query Object.
11 : *
12 : * This class was originally adopted from rdfapi-php (@link http://sourceforge.net/projects/rdfapi-php/).
13 : * It was modified and extended in order to fit into Erfurt.
14 : *
15 : * @package Erfurt_Sparql
16 : * @author Tobias Gauss <tobias.gauss@web.de>
17 : * @author Christian Weiske <cweiske@cweiske.de>
18 : * @author Philipp Frischmuth <pfrischmuth@googlemail.com>
19 : * @license http://www.gnu.org/licenses/lgpl.html LGPL
20 : */
21 : class Erfurt_Sparql_Parser
22 : {
23 : // ------------------------------------------------------------------------
24 : // --- Protected static properties ----------------------------------------
25 : // ------------------------------------------------------------------------
26 :
27 : /**
28 : * Which order operators are to be treated (11.3 Operator Mapping).
29 : *
30 : * @var array
31 : */
32 : protected static $_operatorPrecedence = array(
33 : '||' => 0,
34 : '&&' => 1,
35 : '=' => 2,
36 : '!=' => 3,
37 : '<' => 4,
38 : '>' => 5,
39 : '<=' => 6,
40 : '>=' => 7,
41 : '*' => 0,
42 : '/' => 0,
43 : '+' => 0,
44 : '-' => 0,
45 : );
46 :
47 : /**
48 : * Operators introduced by sparql.
49 : *
50 : * @var array
51 : */
52 : protected static $_sops = array('regex', 'bound', 'isuri', 'isblank',
53 : 'isliteral', 'str', 'lang', 'datatype', 'langmatches'
54 : );
55 :
56 : // ------------------------------------------------------------------------
57 : // --- Protected properties -----------------------------------------------
58 : // ------------------------------------------------------------------------
59 :
60 : /**
61 : * The query object.
62 : *
63 : * @var Erfurt_Sparql_Query
64 : */
65 : protected $_query;
66 :
67 : /**
68 : * Last parsed graphPattern.
69 : *
70 : * @var int
71 : */
72 : protected $_tmp;
73 :
74 : /**
75 : * The tokenized query.
76 : *
77 : * @var array
78 : */
79 : protected $_tokens = array();
80 :
81 : /**
82 : * Contains blank node ids as key and an boolean value as value.
83 : * This array is used in order to invalidate used blank node ids in some
84 : * cases.
85 : */
86 : protected $_usedBlankNodes = array();
87 :
88 : // ------------------------------------------------------------------------
89 : // --- Public static methods ----------------------------------------------
90 : // ------------------------------------------------------------------------
91 :
92 : /**
93 : * Tokenizes the query string into $tokens.
94 : * The query may not contain any comments.
95 : *
96 : * @param string $queryString Query to split into tokens
97 : * @return array Tokens
98 : */
99 : public static function tokenize($queryString)
100 : {
101 335 : $inTelUri = false;
102 335 : $inUri = false;
103 :
104 335 : $queryString = trim($queryString);
105 :
106 335 : $removeableSpecialChars = array(' ', "\t", "\r", "\n");
107 335 : $specialChars = array(',', '\\', '(', ')', '{', '}', '"', "'", ';', '[', ']');
108 :
109 335 : $len = strlen($queryString);
110 335 : $tokens = array();
111 335 : $n = 0;
112 :
113 335 : $inLiteral = false;
114 335 : $longLiteral = false;
115 :
116 335 : for ($i=0; $i<$len; ++$i) {
117 335 : if (in_array($queryString[$i], $removeableSpecialChars) && !$inLiteral && !$inTelUri) {
118 335 : if (isset($tokens[$n]) && $tokens[$n] !== '') {
119 335 : if ((strlen($tokens[$n]) >= 2)) {
120 335 : if (($tokens[$n][strlen($tokens[$n])-1] === '.') &&
121 335 : !is_numeric(substr($tokens[$n], 0, strlen($tokens[$n])-1))) {
122 :
123 16 : $tokens[$n] = substr($tokens[$n], 0, strlen($tokens[$n])-1);
124 16 : $tokens[++$n] = '.';
125 335 : } else if (($tokens[$n][0] === '.')) {
126 0 : $dummy = substr($tokens[$n], 1);
127 0 : $tokens[$n] = '.';
128 0 : $tokens[++$n] = $dummy;
129 0 : }
130 335 : }
131 :
132 335 : $n++;
133 335 : }
134 :
135 335 : continue;
136 335 : } else if (in_array($queryString[$i], $specialChars) && !$inTelUri && !$inUri) {
137 334 : if ($queryString[$i] === '"' || $queryString[$i] === "'") {
138 78 : $foundChar = $queryString[$i];
139 78 : if (!$inLiteral) {
140 : // Check for long literal
141 78 : if (($queryString[($i+1)] === $foundChar) && ($queryString[($i+2)] === $foundChar)) {
142 12 : $longLiteral = true;
143 12 : }
144 :
145 78 : $inLiteral = true;
146 78 : } else {
147 : // We are inside a literal... Check whether this is the end of the literal.
148 78 : if ($longLiteral) {
149 12 : if (($queryString[($i+1)] === $foundChar) && ($queryString[($i+2)] === $foundChar)) {
150 12 : $inLiteral = false;
151 12 : $longLiteral = false;
152 12 : }
153 12 : } else {
154 66 : $inLiteral = false;
155 : }
156 : }
157 78 : }
158 :
159 334 : if (isset($tokens[$n]) && ($tokens[$n] !== '')) {
160 : // Check whether trailing char is a dot.
161 157 : if ((strlen($tokens[$n]) >= 2)) {
162 146 : if (($tokens[$n][strlen($tokens[$n])-1] === '.') &&
163 146 : !is_numeric(substr($tokens[$n], 0, strlen($tokens[$n])-1))) {
164 :
165 0 : $tokens[$n] = substr($tokens[$n], 0, strlen($tokens[$n])-1);
166 0 : $tokens[++$n] = '.';
167 146 : } else if (($tokens[$n][0] === '.')) {
168 1 : $dummy = substr($tokens[$n], 1);
169 1 : $tokens[$n] = '.';
170 1 : $tokens[++$n] = $dummy;
171 1 : }
172 146 : }
173 :
174 157 : $tokens[++$n] = '';
175 157 : }
176 :
177 : // In case we have a \ in the string we add the following char to the current token.
178 : // In that case it doesn't matter what type of char the following one is!
179 334 : if ($queryString[$i] === '\\') {
180 : // Escaped chars do not need a new token.
181 16 : $n--;
182 :
183 16 : $tokens[$n] .= $queryString[$i] . $queryString[++$i];
184 :
185 : // In case we have added \u we will also add the next 4 digits.
186 16 : if ($queryString[$i] === 'u') {
187 2 : $tokens[$n] .= $queryString[++$i] . $queryString[++$i]. $queryString[++$i]. $queryString[++$i];
188 2 : }
189 :
190 16 : $n++;
191 16 : }
192 : // Sparql supports literals that are written as """...""" in order to support quotation inside
193 : // the literal.
194 334 : else if (($queryString[$i] === '"') && ($i<($len-2)) && ($queryString[($i+1)] === '"') &&
195 334 : ($queryString[($i+2)] === '"')) {
196 7 : $tokens[$n++] = $queryString[$i] . $queryString[++$i] . $queryString[++$i];
197 7 : }
198 : // Sparql supports literals that are written as '''...''' in order to support quotation inside
199 : // the literal.
200 334 : else if (($queryString[$i] === "'") && ($i<($len-2)) && ($queryString[($i+1)] === "'") &&
201 334 : ($queryString[($i+2)] === "'")) {
202 6 : $tokens[$n++] = $queryString[$i] . $queryString[++$i] . $queryString[++$i];
203 6 : } else {
204 334 : $tokens[$n++] = $queryString[$i];
205 : }
206 334 : } else {
207 : // Special care for tel URIs
208 335 : if (substr($queryString, $i, 5) === '<tel:') {
209 0 : $inTelUri = true;
210 0 : }
211 335 : if ($inTelUri && $queryString[$i] === '>') {
212 0 : $inTelUri = false;
213 0 : }
214 :
215 :
216 335 : if (!isset($tokens[$n])) {
217 335 : $tokens[$n] = '';
218 335 : }
219 :
220 : // Iris written as <><><> can be written without whitespace, so we need to test for this.
221 : // If yes, we need to start a new token.
222 335 : if ((substr($tokens[$n], 0, 1) === '<') && ($queryString[$i] === '>')) {
223 262 : $tokens[$n++] .= $queryString[$i];
224 262 : $inUri = false;
225 262 : continue;
226 335 : } else if ($queryString[$i] === '<') {
227 262 : $inUri = true;
228 262 : if ($tokens[$n] === '') {
229 262 : $tokens[$n] = '<';
230 262 : continue;
231 : } else {
232 1 : $tokens[++$n] = '<';
233 1 : continue;
234 : }
235 :
236 : }
237 :
238 335 : $tokens[$n] .= $queryString{$i};
239 : }
240 335 : }
241 :
242 335 : return $tokens;
243 : }
244 :
245 : /**
246 : * Removes comments in the query string. Comments are
247 : * indicated by '#'.
248 : *
249 : * @param string $queryString
250 : * @return string The uncommented query string
251 : */
252 : public static function uncomment($queryString)
253 : {
254 335 : $regex = "/((\"[^\"]*\")|(\'[^\']*\')|(\<[^\>]*\>))|(#.*)/";
255 :
256 335 : return preg_replace($regex, '\1', $queryString);
257 : }
258 :
259 : // ------------------------------------------------------------------------
260 : // --- Protected static methods -------------------------------------------
261 : // ------------------------------------------------------------------------
262 :
263 : /**
264 : * "Balances" the filter tree in the way that operators on the same
265 : * level are nested according to their precedence defined in
266 : * $operatorPrecedence array.
267 : *
268 : * @param array $tree Tree to be modified
269 : */
270 : public static function balanceTree(&$tree)
271 : {
272 :
273 46 : if (isset($tree['type']) && $tree['type'] === 'equation' && isset($tree['operand1']['type']) &&
274 46 : $tree['operand1']['type'] === 'equation' && $tree['level'] === $tree['operand1']['level'] &&
275 46 : self::$_operatorPrecedence[$tree['operator']] > self::$_operatorPrecedence[$tree['operand1']['operator']]) {
276 :
277 : $op2 = array(
278 4 : 'type' => 'equation',
279 4 : 'level' => $tree['level'],
280 4 : 'operator' => $tree['operator'],
281 4 : 'operand1' => $tree['operand1']['operand2'],
282 4 : 'operand2' => $tree['operand2']
283 4 : );
284 4 : $tree['operator'] = $tree['operand1']['operator'];
285 4 : $tree['operand1'] = $tree['operand1']['operand1'];
286 4 : $tree['operand2'] = $op2;
287 4 : }
288 46 : }
289 :
290 : public static function fixNegationInFuncName(&$tree)
291 : {
292 60 : if ($tree['type'] === 'function' && $tree['name'][0] === '!') {
293 4 : $tree['name'] = substr($tree['name'], 1);
294 :
295 4 : if (!isset($tree['negated'])) {
296 4 : $tree['negated'] = true;
297 4 : } else {
298 0 : unset($tree['negated']);
299 : }
300 : //perhaps more !!
301 4 : self::fixNegationInFuncName($tree);
302 4 : }
303 60 : }
304 :
305 : // ------------------------------------------------------------------------
306 : // --- Public methods -----------------------------------------------------
307 : // ------------------------------------------------------------------------
308 :
309 : /**
310 : * Main function of Erfurt_Sparql_Parser.
311 : * Parses a query string.
312 : *
313 : * @param string $queryString The SPARQL query
314 : * @return Erfurt_Sparql_Query The query object
315 : * @throws Erfurt_Sparql_ParserException
316 : */
317 : public function parse($queryString = false)
318 : {
319 334 : if ($queryString === false) {
320 0 : require_once 'Erfurt/Sparql/ParserException.php';
321 0 : throw new Erfurt_Sparql_ParserException('Querystring is empty.');
322 : }
323 : //echo "Parser is called on:\n".$queryString."\n\n";
324 334 : $this->_prepare();
325 334 : $this->_query->setQueryString($queryString);
326 :
327 334 : $uncommented = self::uncomment($queryString);
328 334 : $this->_tokens = self::tokenize($uncommented);
329 :
330 334 : $this->_parseQuery();
331 :
332 334 : if (!$this->_query->isComplete()) {
333 0 : require_once 'Erfurt/Sparql/ParserException.php';
334 0 : throw new Erfurt_Sparql_ParserException('Query is incomplete.');
335 : }
336 :
337 334 : return $this->_query;
338 : }
339 :
340 : // ------------------------------------------------------------------------
341 : // --- Protected methods --------------------------------------------------
342 : // ------------------------------------------------------------------------
343 :
344 : /**
345 : * Checks if $token is a Blanknode.
346 : *
347 : * @param string $token The token
348 : * @return boolean true if the token is BNode false if not
349 : */
350 : protected function _bNodeCheck($token)
351 : {
352 314 : if (substr($token, 0, 2) === '_:') {
353 30 : return true;
354 : }
355 :
356 312 : return false;
357 : }
358 :
359 : /**
360 : * Checks if there is a datatype given and appends it to the node.
361 : *
362 : * @param string $node Node to check
363 : */
364 : protected function _checkDtypeLang(&$node, $nSubstrLength = 1)
365 : {
366 45 : $this->_fastForward();
367 45 : switch (substr(current($this->_tokens), 0, 1)) {
368 45 : case '^':
369 6 : if (substr(current($this->_tokens), 0, 2) === '^^') {
370 6 : if (strlen(current($this->_tokens)) === 2) {
371 1 : next($this->_tokens);
372 1 : }
373 :
374 6 : require_once 'Erfurt/Rdf/Literal.php';
375 6 : $node = Erfurt_Rdf_Literal::initWithLabel(substr($node, 1, -1));
376 6 : $node->setDatatype(
377 6 : $this->_query->getFullUri(
378 6 : substr(current($this->_tokens), 2)
379 6 : )
380 6 : );
381 6 : }
382 6 : break;
383 39 : case '@':
384 1 : require_once 'Erfurt/Rdf/Literal.php';
385 1 : $node = Erfurt_Rdf_Literal::initWithLabelAndLanguage(
386 1 : substr($node, $nSubstrLength, -$nSubstrLength),
387 1 : substr(current($this->_tokens), $nSubstrLength)
388 1 : );
389 1 : break;
390 38 : default:
391 38 : prev($this->_tokens);
392 38 : require_once 'Erfurt/Rdf/Literal.php';
393 38 : $node = Erfurt_Rdf_Literal::initWithLabel(substr($node, $nSubstrLength, -$nSubstrLength));
394 38 : break;
395 45 : }
396 45 : }
397 :
398 :
399 : protected function _dissallowBlankNodes()
400 : {
401 333 : foreach ($this->_usedBlankNodes as $key => &$value) {
402 30 : $value = false;
403 333 : }
404 333 : }
405 :
406 : /**
407 : * Checks if the Node is a typed Literal.
408 : *
409 : * @param String $node
410 : * @return boolean true if typed, false if not.
411 : */
412 : protected function _dtypeCheck(&$node)
413 : {
414 314 : $patternInt = "/^-?[0-9]+$/";
415 314 : $match = preg_match($patternInt,$node,$hits);
416 314 : if ($match > 0) {
417 13 : require_once 'Erfurt/Rdf/Literal.php';
418 13 : $node = Erfurt_Rdf_Literal::initWithLabel($hits[0]);
419 13 : $node->setDatatype(EF_XSD_NS.'integer');
420 13 : return true;
421 : }
422 :
423 314 : $patternBool = "/^(true|false)$/";
424 314 : $match = preg_match($patternBool,$node,$hits);
425 314 : if ($match>0) {
426 0 : require_once 'Erfurt/Rdf/Literal.php';
427 0 : $node = Erfurt_Rdf_Literal::initWithLabel($hits[0]);
428 0 : $node->setDatatype(EF_XSD_NS.'boolean');
429 0 : return true;
430 : }
431 :
432 314 : $patternType = "/^a$/";
433 314 : $match = preg_match($patternType,$node,$hits);
434 314 : if ($match>0) {
435 9 : require_once 'Erfurt/Rdf/Resource.php';
436 9 : $node = Erfurt_Rdf_Resource::initWithNamespaceAndLocalName(EF_RDF_NS, 'type');
437 9 : return true;
438 : }
439 :
440 314 : $patternDouble = "/^-?[0-9]+.[0-9]+[e|E]?-?[0-9]*/";
441 314 : $match = preg_match($patternDouble,$node,$hits);
442 314 : if ($match>0) {
443 6 : require_once 'Erfurt/Rdf/Literal.php';
444 6 : $node = Erfurt_Rdf_Literal::initWithLabel($hits[0]);
445 6 : $node->setDatatype(EF_XSD_NS.'double');
446 6 : return true;
447 : }
448 314 : return false;
449 : }
450 :
451 : /** FastForward until next token which is not blank. */
452 : protected function _fastForward()
453 : {
454 : #next($this->_tokens);
455 : #return;
456 :
457 334 : $tok = next($this->_tokens);
458 334 : while ($tok === ' ') {
459 3 : $tok = next($this->_tokens);
460 3 : }
461 334 : }
462 :
463 : /**
464 : * Checks if $token is an IRI.
465 : *
466 : * @param string $token The token
467 : * @return boolean true if the token is an IRI false if not
468 : */
469 : protected function _iriCheck($token)
470 : {
471 326 : $pattern = "/^<[^>]*>\.?$/";
472 :
473 326 : if (preg_match($pattern, $token) > 0) {
474 257 : return true;
475 : }
476 :
477 305 : return false;
478 : }
479 :
480 : /**
481 : * Checks if $token is a Literal.
482 : *
483 : * @param string $token The token
484 : * @return boolean true if the token is a Literal false if not
485 : */
486 : protected function _literalCheck($token)
487 : {
488 268 : $pattern = "/^[\"\'].*$/";
489 268 : if (preg_match($pattern, $token) > 0) {
490 45 : return true;
491 246 : } else if (is_numeric($token)) {
492 3 : return true;
493 : }
494 :
495 243 : return false;
496 : }
497 :
498 : /**
499 : * Sets result form to 'ASK' and 'COUNT'.
500 : *
501 : * @param string $form if it's an ASK or COUNT query
502 : */
503 : protected function _parseAsk($form)
504 : {
505 7 : $this->_query->setResultForm($form);
506 :
507 7 : $this->_fastForward();
508 :
509 7 : if (current($this->_tokens) === '{' || strtolower(current($this->_tokens)) === 'from') {
510 7 : prev($this->_tokens);
511 7 : }
512 7 : }
513 :
514 : /**
515 : * Parses the BASE part of the query.
516 : *
517 : * @throws Erfurt_Sparql_ParserException
518 : */
519 : protected function _parseBase()
520 : {
521 28 : $this->_fastForward();
522 28 : if ($this->_iriCheck(current($this->_tokens))) {
523 28 : $this->_query->setBase(current($this->_tokens));
524 28 : } else {
525 0 : require_once 'Erfurt/Sparql/ParserException.php';
526 0 : throw new Erfurt_Sparql_ParserException('IRI expected', -1, key($this->_tokens));
527 : }
528 28 : }
529 :
530 : /**
531 : * Parses an RDF collection.
532 : *
533 : * @param Erfurt_Sparql_TriplePattern $trp
534 : * @return Erfurt_Rdf_Node The first parsed label.
535 : */
536 : protected function _parseCollection(&$trp)
537 : {
538 12 : if (prev($this->_tokens) === '{') {
539 11 : $prevStart = true;
540 11 : } else {
541 4 : $prevStart = false;
542 : }
543 12 : next($this->_tokens);
544 :
545 12 : $tmpLabel = $this->_query->getBlanknodeLabel();
546 12 : $firstLabel = $this->_parseNode($tmpLabel);
547 12 : $this->_fastForward();
548 12 : $i = 0;
549 12 : $emptyList = true;
550 :
551 12 : require_once 'Erfurt/Rdf/Resource.php';
552 12 : $rdfRest = Erfurt_Rdf_Resource::initWithNamespaceAndLocalName(EF_RDF_NS, 'rest');
553 12 : $rdfFirst = Erfurt_Rdf_Resource::initWithNamespaceAndLocalName(EF_RDF_NS, 'first');
554 12 : $rdfNil = Erfurt_Rdf_Resource::initWithNamespaceAndLocalName(EF_RDF_NS, 'nil');
555 :
556 12 : require_once 'Erfurt/Sparql/QueryTriple.php';
557 12 : while (current($this->_tokens) !== ')') {
558 9 : if ($i>0) {
559 4 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfRest,
560 4 : $this->_parseNode($tmpLabel = $this->_query->getBlanknodeLabel()));
561 4 : }
562 :
563 9 : if (current($this->_tokens) === '(') {
564 2 : $listNode = $this->_parseCollection($trp);
565 :
566 2 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfFirst, $listNode);
567 9 : } else if (current($this->_tokens) === '[') {
568 2 : $this->_fastForward();
569 2 : if (current($this->_tokens) === ']') {
570 1 : $this->_rewind();
571 1 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfFirst, $this->_parseNode());
572 1 : } else {
573 1 : $this->_rewind();
574 :
575 1 : $sNode = $this->_parseNode();
576 1 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfFirst, $sNode);
577 :
578 1 : $this->_fastForward();
579 1 : $p = $this->_parseNode();
580 :
581 1 : $this->_fastForward();
582 1 : $o = $this->_parseNode();
583 :
584 1 : $trp[] = new Erfurt_Sparql_QueryTriple($sNode, $p, $o);
585 1 : $this->_fastForward();
586 : }
587 2 : } else {
588 7 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfFirst, $this->_parseNode());
589 : }
590 :
591 9 : $this->_fastForward();
592 9 : $emptyList = false;
593 9 : $i++;
594 9 : }
595 :
596 12 : if ($prevStart && $emptyList) {
597 3 : if (next($this->_tokens) === '}') {
598 : // list may not occure standalone in a pattern.
599 0 : require_once 'Erfurt/Sparql/ParserException.php';
600 0 : throw new Erfurt_Sparql_ParserException(
601 0 : 'A list may not occur standalone in a pattern.', -1, key($this->_tokens));
602 : }
603 3 : prev($this->_tokens);
604 3 : }
605 :
606 12 : $trp[] = new Erfurt_Sparql_QueryTriple($this->_parseNode($tmpLabel), $rdfRest, $rdfNil);
607 12 : return $firstLabel;
608 : }
609 :
610 : /**
611 : * Parses a value constraint.
612 : *
613 : * @param GraphPattern $pattern
614 : * @param boolean $outer If the constraint is an outer one.
615 : */
616 : protected function _parseConstraint(&$pattern, $outer)
617 : {
618 91 : require_once 'Erfurt/Sparql/Constraint.php';
619 91 : $constraint = new Erfurt_Sparql_Constraint();
620 91 : $constraint->setOuterFilter($outer);
621 :
622 91 : $constraint->setTree($this->_parseConstraintTree());
623 :
624 91 : if (current($this->_tokens) === '}') {
625 49 : prev($this->_tokens);
626 49 : }
627 :
628 91 : $pattern->addConstraint($constraint);
629 91 : }
630 :
631 : /**
632 : * Parses a constraint string recursively.
633 : *
634 : * The result array is one "element" which may contain subelements.
635 : * All elements have one key "type" that determines which other
636 : * array keys the element array has. Valid types are:
637 : * - "value":
638 : * Just a plain value with a value key, nothing else
639 : * - "function"
640 : * A function has a name and an array of parameter(s). Each parameter
641 : * is an element.
642 : * - "equation"
643 : * An equation has an operator, and operand1 and operand2 which
644 : * are elements themselves
645 : * Any element may have the "negated" value set to true, which means
646 : * that is is - negated (!).
647 : *
648 : * @internal The functionality of this method is being unit-tested
649 : * in testSparqlParserTests::testParseFilter()
650 : * "equation'-elements have another key "level" which is to be used
651 : * internally only.
652 : *
653 : * @return array Nested tree array representing the filter
654 : */
655 : protected function _parseConstraintTree($nLevel = 0, $bParameter = false)
656 : {
657 91 : $tree = array();
658 91 : $part = array();
659 91 : $chQuotes = null;
660 91 : $litQuotes = null;
661 91 : $strQuoted = '';
662 91 : $parens = false;
663 :
664 91 : while ($tok = next($this->_tokens)) {
665 91 : if ($chQuotes !== null && $tok != $chQuotes) {
666 31 : $strQuoted .= $tok;
667 31 : continue;
668 91 : } else if ($litQuotes !== null) {
669 0 : $strQuoted .= $tok;
670 0 : if ($tok[strlen($tok) - 1] === '>') {
671 0 : $tok = '>';
672 0 : } else {
673 0 : continue;
674 : }
675 91 : } else if ($tok === ')' || $tok === '}' || $tok === '.') {
676 91 : break;
677 91 : } else if (strtolower($tok) === 'filter' || strtolower($tok) === 'optional') {
678 6 : break;
679 : }
680 : switch ($tok) {
681 91 : case '"':
682 91 : case '\'':
683 32 : if ($chQuotes === null) {
684 32 : $chQuotes = $tok;
685 32 : $strQuoted = '';
686 32 : } else {
687 32 : $chQuotes = null;
688 32 : $part[] = array(
689 32 : 'type' => 'value',
690 32 : 'value' => $strQuoted,
691 : 'quoted'=> true
692 32 : );
693 : }
694 32 : continue 2;
695 : break;
696 : # case '>':
697 : # $litQuotes = null;
698 : # $part[] = array(
699 : # 'type' => 'value',
700 : # 'value' => $strQuoted,
701 : # 'quoted'=> false
702 : # );
703 : # continue 2;
704 : # break;
705 91 : case '(':
706 91 : $parens = true;
707 91 : $bFunc1 = isset($part[0]['type']) && $part[0]['type'] === 'value';
708 91 : $bFunc2 = isset($tree['type']) && $tree['type'] === 'equation'
709 91 : && isset($tree['operand2']) && isset($tree['operand2']['value']);
710 91 : $part[] = $this->_parseConstraintTree(
711 91 : $nLevel + 1,
712 0 : $bFunc1 || $bFunc2
713 91 : );
714 :
715 91 : if ($bFunc1) {
716 58 : $tree['type'] = 'function';
717 58 : $tree['name'] = $part[0]['value'];
718 58 : self::fixNegationInFuncName($tree);
719 58 : if (isset($part[1]['type'])) {
720 0 : $part[1] = array($part[1]);
721 0 : }
722 58 : $tree['parameter'] = $part[1];
723 58 : $part = array();
724 91 : } else if ($bFunc2) {
725 11 : $tree['operand2']['type'] = 'function';
726 11 : $tree['operand2']['name'] = $tree['operand2']['value'];
727 11 : self::fixNegationInFuncName($tree['operand2']);
728 11 : $tree['operand2']['parameter'] = $part[0];
729 11 : unset($tree['operand2']['value']);
730 11 : unset($tree['operand2']['quoted']);
731 11 : $part = array();
732 11 : }
733 :
734 91 : if (current($this->_tokens) === ')') {
735 90 : if (substr(next($this->_tokens), 0, 2) === '_:') {
736 : // filter ends here
737 1 : prev($this->_tokens);
738 1 : break 2;
739 : } else {
740 89 : prev($this->_tokens);
741 : }
742 89 : }
743 :
744 90 : continue 2;
745 : break;
746 91 : case ' ':
747 91 : case "\t":
748 0 : continue 2;
749 91 : case '=':
750 91 : case '>':
751 91 : case '<':
752 91 : case '<=':
753 91 : case '>=':
754 91 : case '-' : //TODO: check correctness
755 91 : case '+' : //TODO: check correctness
756 91 : case '!=':
757 91 : case '&&':
758 91 : case '||':
759 46 : if (isset($tree['type']) && $tree['type'] === 'equation' && isset($tree['operand2'])) {
760 : //previous equation open
761 4 : $part = array($tree);
762 46 : } else if (isset($tree['type']) && $tree['type'] !== 'equation') {
763 23 : $part = array($tree);
764 23 : $tree = array();
765 23 : }
766 :
767 46 : $tree['type'] = 'equation';
768 46 : $tree['level'] = $nLevel;
769 46 : $tree['operator'] = $tok;
770 : //TODO: remove the if when parse contraint is fixed (issue 601)
771 46 : if(isset($part[0])) $tree['operand1'] = $part[0]; else $tree['operand1'] = null;
772 46 : unset($tree['operand2']);
773 46 : $part = array();
774 46 : continue 2;
775 : break;
776 91 : case '!':
777 6 : if ($tree != array()) {
778 0 : require_once 'Erfurt/Sparql/ParserException.php';
779 0 : throw new Erfurt_Sparql_ParserException(
780 0 : 'Unexpected "!" negation in constraint.', -1, current($this->_tokens));
781 : }
782 6 : $tree['negated'] = true;
783 6 : continue 2;
784 91 : case ',':
785 : //parameter separator
786 21 : if (count($part) == 0 && !isset($tree['type'])) {
787 0 : throw new SparqlParserException(
788 : 'Unexpected comma'
789 0 : );
790 : }
791 21 : $bParameter = true;
792 21 : if (count($part) === 0) {
793 6 : $part[] = $tree;
794 6 : $tree = array();
795 6 : }
796 21 : continue 2;
797 91 : default:
798 91 : break;
799 91 : }
800 :
801 91 : if ($this->_varCheck($tok)) {
802 86 : if (!$parens && $nLevel === 0) {
803 : // Variables need parenthesizes first
804 0 : require_once 'Erfurt/Sparql/ParserException.php';
805 0 : throw new Erfurt_Sparql_ParserException('FILTER expressions that start with a variable need parenthesizes.', -1, current($this->_tokens));
806 : }
807 :
808 86 : $part[] = array(
809 86 : 'type' => 'value',
810 86 : 'value' => $tok,
811 : 'quoted' => false
812 86 : );
813 91 : } else if (substr($tok, 0, 2) === '_:') {
814 : // syntactic blank nodes not allowed in filter
815 0 : require_once 'Erfurt/Sparql/ParserException.php';
816 0 : throw new Erfurt_Sparql_ParserException('Syntactic Blanknodes not allowed in FILTER.', -1,
817 0 : current($this->_tokens));
818 73 : } else if (substr($tok, 0, 2) === '^^') {
819 2 : $part[count($part) - 1]['datatype'] = $this->_query->getFullUri(substr($tok, 2));
820 73 : } else if ($tok[0] === '@') {
821 2 : $part[count($part) - 1]['language'] = substr($tok, 1);
822 71 : } else if ($tok[0] === '<') {
823 15 : if ($tok[strlen($tok) - 1] === '>') {
824 : //single-tokenized <> uris
825 15 : $part[] = array(
826 15 : 'type' => 'value',
827 15 : 'value' => $tok,
828 : 'quoted' => false
829 15 : );
830 15 : } else {
831 : //iris split over several tokens
832 0 : $strQuoted = $tok;
833 0 : $litQuotes = true;
834 : }
835 70 : } else if ($tok === 'true' || $tok === 'false') {
836 2 : $part[] = array(
837 2 : 'type' => 'value',
838 2 : 'value' => $tok,
839 2 : 'quoted' => false,
840 : 'datatype' => 'http://www.w3.org/2001/XMLSchema#boolean'
841 2 : );
842 2 : } else {
843 68 : $part[] = array(
844 68 : 'type' => 'value',
845 68 : 'value' => $tok,
846 : 'quoted' => false
847 68 : );
848 : }
849 91 : if (isset($tree['type']) && $tree['type'] === 'equation' && isset($part[0])) {
850 35 : $tree['operand2'] = $part[0];
851 35 : self::balanceTree($tree);
852 35 : $part = array();
853 35 : }
854 91 : }
855 :
856 91 : if (!isset($tree['type']) && $bParameter) {
857 60 : return $part;
858 91 : } else if (isset($tree['type']) && $tree['type'] === 'equation'
859 91 : && isset($tree['operand1']) && !isset($tree['operand2'])
860 91 : && isset($part[0])) {
861 15 : $tree['operand2'] = $part[0];
862 15 : self::balanceTree($tree);
863 15 : }
864 :
865 91 : if ((count($tree) === 0) && (count($part) > 1)) {
866 0 : require_once 'Erfurt/Sparql/ParserException.php';
867 : //TODO: uncomment when issue 601 is fixed
868 : //throw new Erfurt_Sparql_ParserException('Failed to parse constraint.', -1, current($this->_tokens));
869 0 : }
870 :
871 91 : if (!isset($tree['type']) && isset($part[0])) {
872 73 : if (isset($tree['negated'])) {
873 3 : $part[0]['negated'] = true;
874 3 : }
875 73 : return $part[0];
876 : }
877 :
878 80 : return $tree;
879 : }
880 :
881 : /**
882 : * Parses the CONSTRUCT clause.
883 : *
884 : * @throws Erfurt_Sparql_ParserException
885 : */
886 : protected function _parseConstruct()
887 : {
888 9 : $this->_fastForward();
889 9 : $this->_query->setResultForm('construct');
890 :
891 9 : if (current($this->_tokens) === '{') {
892 9 : $this->_parseGraphPattern(false, false, false, true);
893 9 : } else {
894 0 : require_once 'Erfurt/Sparql/ParserException.php';
895 0 : throw new Erfurt_Sparql_ParserException('Unable to parse CONSTRUCT part. "{" expected.', -1,
896 0 : key($this->_tokens));
897 : }
898 :
899 9 : while (true) {
900 9 : if (strtolower(current($this->_tokens)) === 'from') {
901 0 : $this->_parseFrom();
902 0 : } else {
903 9 : break;
904 : }
905 0 : }
906 :
907 9 : $this->_parseWhere();
908 9 : $this->_parseModifier();
909 9 : }
910 :
911 : /** Adds a new variable to the query and sets result form to 'DESCRIBE'. */
912 : protected function _parseDescribe()
913 : {
914 2 : while (strtolower(current($this->_tokens)) != 'from' && strtolower(current($this->_tokens)) != 'where') {
915 2 : $this->_fastForward();
916 2 : if ($this->_varCheck(current($this->_tokens)) || $this->_iriCheck(current($this->_tokens))) {
917 2 : require_once 'Erfurt/Sparql/QueryResultVariable.php';
918 2 : $var = new Erfurt_Sparql_QueryResultVariable(current($this->_tokens));
919 :
920 2 : $this->_query->addResultVar($var);
921 2 : if (!$this->_query->getResultForm()) {
922 2 : $this->_query->setResultForm('describe');
923 2 : }
924 2 : }
925 :
926 2 : if (!current($this->_tokens)) {
927 1 : break;
928 : }
929 2 : }
930 2 : prev($this->_tokens);
931 2 : }
932 :
933 : /**
934 : * Parses the FROM clause.
935 : *
936 : * @throws Erfurt_Sparql_ParserException
937 : */
938 : protected function _parseFrom()
939 : {
940 25 : $this->_fastForward();
941 :
942 25 : if (strtolower(current($this->_tokens)) !== 'named') {
943 23 : if ($this->_iriCheck(current($this->_tokens)) || $this->_qnameCheck(current($this->_tokens))) {
944 23 : $this->_query->addFrom(substr(current($this->_tokens), 1, -1));
945 23 : } else if ($this->_varCheck(current($this->_tokens))) {
946 0 : $this->_query->addFrom(current($this->_tokens));
947 0 : } else {
948 0 : require_once 'Erfurt/Sparql/ParserException.php';
949 0 : throw new Erfurt_Sparql_ParserException('Variable, iri or qname expected in FROM', -1,
950 0 : key($this->_tokens));
951 : }
952 23 : } else {
953 3 : $this->_fastForward();
954 3 : if ($this->_iriCheck(current($this->_tokens)) || $this->_qnameCheck(current($this->_tokens))) {
955 3 : $this->_query->addFromNamed(substr(current($this->_tokens), 1, -1));
956 3 : } else if ($this->_varCheck(current($this->_tokens))) {
957 0 : $this->_query->addFromNamed(current($this->_tokens));
958 0 : } else {
959 0 : require_once 'Erfurt/Sparql/ParserException.php';
960 0 : throw new Erfurt_Sparql_ParserException('Variable, Iri or qname expected in FROM NAMED', -1,
961 0 : key($this->_tokens));
962 : }
963 : }
964 25 : }
965 :
966 : /**
967 : * Parses a GRAPH clause.
968 : *
969 : * @param Erfurt_Sparql_GraphPattern $pattern
970 : * @throws Erfurt_Sparql_ParserException
971 : */
972 : protected function _parseGraph()
973 : {
974 9 : $this->_fastForward();
975 9 : $name = current($this->_tokens);
976 9 : if (!$this->_varCheck($name) && !$this->_iriCheck($name) && !$this->_qnameCheck($name)) {
977 0 : $msg = $name;
978 0 : $msg = preg_replace('/</', '<', $msg);
979 0 : require_once 'Erfurt/Sparql/ParserException.php';
980 0 : throw new Erfurt_Sparql_ParserException('IRI or Var expected.', -1, key($this->_tokens));
981 : }
982 9 : $this->_fastForward();
983 :
984 9 : if ($this->_iriCheck($name)) {
985 0 : require_once 'Erfurt/Rdf/Resource.php';
986 0 : $name = Erfurt_Rdf_Resource::initWithIri(substr($name,1,-1));
987 9 : } else if($this->_qnameCheck($name)) {
988 3 : require_once 'Erfurt/Rdf/Resource.php';
989 3 : $name = Erfurt_Rdf_Resource::initWithIri($this->_query->getFullUri($name));
990 3 : }
991 9 : $this->_parseGraphPattern(false, false, $name);
992 9 : if (current($this->_tokens) === '.') {
993 1 : $this->_fastForward();
994 1 : }
995 9 : }
996 :
997 : /**
998 : * Parses a graph pattern.
999 : *
1000 : * @param int $optional Optional graph pattern
1001 : * @param int $union Union graph pattern
1002 : * @param string $graph Graphname
1003 : * @param boolean $constr TRUE if the pattern is a construct pattern
1004 : * @param boolean $external If the parsed pattern shall be returned
1005 : * @param int $subpattern If the new pattern is subpattern of the pattern with the given id
1006 : */
1007 : protected function _parseGraphPattern($optional = false, $union = false, $graph = false, $constr = false,
1008 : $external = false, $subpattern = false)
1009 : {
1010 333 : $pattern = $this->_query->getNewPattern($constr);
1011 :
1012 333 : if (current($this->_tokens) !== '{') {
1013 0 : require_once 'Erfurt/Sparql/ParserException.php';
1014 0 : throw new Erfurt_Sparql_ParserException(
1015 0 : 'A graph pattern needs to start with "{".', -1, key($this->_tokens));
1016 : }
1017 :
1018 : // A new graph pattern invalidates the use of all previous blank nodes.
1019 333 : $this->_dissallowBlankNodes();
1020 :
1021 333 : if (is_int($optional)) {
1022 43 : $pattern->setOptional($optional);
1023 43 : } else {
1024 333 : $this->_tmp = $pattern->getId();
1025 : }
1026 :
1027 333 : if (is_int($union)) {
1028 13 : $pattern->setUnion($union);
1029 13 : }
1030 :
1031 333 : if (is_int($subpattern)) {
1032 14 : $pattern->setSubpatternOf($subpattern);
1033 14 : }
1034 :
1035 333 : if ($graph != false) {
1036 9 : $pattern->setGraphname($graph);
1037 9 : }
1038 :
1039 333 : $this->_fastForward();
1040 :
1041 : do {
1042 333 : switch (strtolower(current($this->_tokens))) {
1043 333 : case 'graph':
1044 6 : $this->_parseGraph();
1045 6 : $this->_dissallowBlankNodes();
1046 6 : break;
1047 333 : case 'union':
1048 11 : $this->_fastForward();
1049 11 : $this->_parseGraphPattern(false, $this->_tmp, false, false, false, $subpattern);
1050 11 : break;
1051 333 : case 'optional':
1052 13 : $this->_fastForward();
1053 13 : $this->_parseGraphPattern($pattern->patternId, false, false, false, false, null);
1054 13 : break;
1055 333 : case 'filter':
1056 10 : $this->_parseConstraint($pattern, true);
1057 :
1058 10 : if (current($this->_tokens) === ')') {
1059 8 : $this->_fastForward();
1060 8 : }
1061 :
1062 10 : $needsDot = false;
1063 10 : break;
1064 333 : case '.':
1065 333 : case ';':
1066 : // Check whether the previous token is {, for this is not allowed.
1067 7 : $this->_rewind();
1068 7 : if (current($this->_tokens) === '{') {
1069 0 : require_once 'Erfurt/Sparql/ParserException.php';
1070 0 : throw new Erfurt_Sparql_ParserException('A dot/semicolon must not follow a "{" directly.', -1,
1071 0 : key($this->_tokens));
1072 : }
1073 7 : $this->_fastForward();
1074 :
1075 7 : $this->_fastForward();
1076 7 : break;
1077 333 : case '{':
1078 12 : $subpattern = $pattern->getId();
1079 12 : $this->_parseGraphPattern(false, false, false, false, false, $subpattern);
1080 12 : break;
1081 333 : case '}':
1082 39 : $pattern->open = false;
1083 39 : break;
1084 314 : default:
1085 314 : $this->_parseTriplePattern($pattern);
1086 314 : break;
1087 333 : }
1088 333 : } while ($pattern->open);
1089 :
1090 333 : if ($external) {
1091 0 : return $pattern;
1092 : }
1093 333 : $this->_fastForward();
1094 333 : }
1095 :
1096 : /**
1097 : * Parses a literal.
1098 : *
1099 : * @param string $node
1100 : * @param string $sep used separator " or '
1101 : */
1102 : protected function _parseLiteral(&$node, $sep)
1103 : {
1104 48 : if ($sep !== null) {
1105 : do {
1106 45 : next($this->_tokens);
1107 45 : $node = $node . current($this->_tokens);
1108 45 : } while ((current($this->_tokens) != $sep));
1109 45 : $this->_checkDtypeLang($node, strlen($sep));
1110 45 : } else {
1111 3 : $datatype = '';
1112 3 : if (is_string($node) && strpos($node, '.') !== false) {
1113 2 : $datatype = EF_XSD_NS . 'integer';
1114 2 : } else {
1115 1 : $datatype = EF_XSD_NS . 'decimal';
1116 : }
1117 :
1118 3 : require_once 'Erfurt/Rdf/Literal.php';
1119 3 : $node = Erfurt_Rdf_Literal::initWithLabel($node);
1120 3 : $node->setDatatype($datatype);
1121 : }
1122 48 : }
1123 :
1124 : /**
1125 : * Parses the solution modifiers of a query.
1126 : *
1127 : * @throws Erfurt_Sparql_ParserException
1128 : */
1129 : protected function _parseModifier()
1130 : {
1131 : do {
1132 333 : switch(strtolower(current($this->_tokens))) {
1133 333 : case 'order':
1134 25 : $this->_fastForward();
1135 25 : if (strtolower(current($this->_tokens)) === 'by') {
1136 25 : $this->_fastForward();
1137 25 : $this->_parseOrderCondition();
1138 25 : } else {
1139 0 : require_once 'Erfurt/Sparql/ParserException.php';
1140 0 : throw new Erfurt_Sparql_ParserException('"BY" expected.', -1, key($this->_tokens));
1141 : }
1142 25 : break;
1143 317 : case 'limit':
1144 13 : $this->_fastForward();
1145 13 : $val = current($this->_tokens);
1146 13 : $this->_query->setSolutionModifier('limit', $val);
1147 13 : break;
1148 308 : case 'offset':
1149 6 : $this->_fastForward();
1150 6 : $val = current($this->_tokens);
1151 6 : $this->_query->setSolutionModifier('offset', $val);
1152 6 : break;
1153 302 : default:
1154 302 : break;
1155 333 : }
1156 333 : } while (next($this->_tokens));
1157 333 : }
1158 :
1159 : /**
1160 : * Parses a String to an RDF node.
1161 : *
1162 : * @param string $node
1163 : * @return Erfurt_Rdf_Node The parsed RDF node
1164 : * @throws Erfurt_Sparql_ParserException
1165 : */
1166 : protected function _parseNode($node = false)
1167 : {
1168 314 : if ($node) {
1169 22 : $node = $node;
1170 22 : } else {
1171 313 : $node = current($this->_tokens);
1172 : }
1173 :
1174 314 : if ($node{strlen($node)-1} === '.') {
1175 1 : $node = substr($node, 0, -1);
1176 1 : }
1177 :
1178 314 : if ($this->_dtypeCheck($node)) {
1179 28 : return $node;
1180 : }
1181 :
1182 :
1183 :
1184 314 : if ($this->_bNodeCheck($node)) {
1185 30 : $node = '?' . $node;
1186 :
1187 30 : if (isset($this->_usedBlankNodes[$node]) && $this->_usedBlankNodes[$node] === false) {
1188 0 : require_once 'Erfurt/Sparql/ParserException.php';
1189 0 : throw new Erfurt_Sparql_ParserException('Reuse of blank node id not allowed here.' -1,
1190 0 : key($this->_tokens));
1191 : }
1192 :
1193 30 : $this->_query->addUsedVar($node);
1194 30 : $this->_usedBlankNodes[$node] = true;
1195 :
1196 30 : return $node;
1197 : }
1198 312 : if ($node === '[') {
1199 2 : $node = '?' . substr($this->_query->getBlanknodeLabel(), 1);
1200 2 : $this->_query->addUsedVar($node);
1201 2 : $this->_fastForward();
1202 2 : if (current($this->_tokens) !== ']') {
1203 1 : prev($this->_tokens);
1204 1 : }
1205 2 : return $node;
1206 : }
1207 :
1208 311 : if ($this->_iriCheck($node)){
1209 60 : $base = $this->_query->getBase();
1210 60 : if ($base != null) {
1211 4 : require_once 'Erfurt/Rdf/Resource.php';
1212 4 : $node = Erfurt_Rdf_Resource::initWithNamespaceAndLocalName(substr($base, 1, -1), substr($node, 1, -1));
1213 4 : } else {
1214 56 : require_once 'Erfurt/Rdf/Resource.php';
1215 56 : $node = Erfurt_Rdf_Resource::initWithIri(substr($node, 1, -1));
1216 : }
1217 60 : return $node;
1218 300 : } else if ($this->_qnameCheck($node)) {
1219 162 : $node = $this->_query->getFullUri($node);
1220 162 : require_once 'Erfurt/Rdf/Resource.php';
1221 162 : $node = Erfurt_Rdf_Resource::initWithIri($node);
1222 162 : return $node;
1223 268 : } else if ($this->_literalCheck($node)) {
1224 48 : if ((substr($node, 0, 1) === '"') || (substr($node, 0, 1) === "'")) {
1225 45 : $ch = substr($node, 0, 1);
1226 45 : $chLong = str_repeat($ch, 3);
1227 45 : if (substr($node, 0, 3) == $chLong) {
1228 12 : $ch = $chLong;
1229 12 : }
1230 45 : $this->_parseLiteral($node, $ch);
1231 45 : } else {
1232 3 : $this->_parseLiteral($node, null);
1233 : }
1234 :
1235 268 : } else if ($this->_varCheck($node)) {
1236 243 : $pos = is_string($node) ? strpos($node, '.') : false;
1237 243 : if ($pos) {
1238 0 : return substr($node,0,$pos);
1239 : } else {
1240 243 : return $node;
1241 : }
1242 0 : } else if ($node[0] === '<') {
1243 : //partial IRI? loop tokens until we find a closing >
1244 0 : while (next($this->_tokens)) {
1245 0 : $node .= current($this->_tokens);
1246 0 : if (substr($node, -1) === '>') {
1247 0 : break;
1248 : }
1249 0 : }
1250 0 : if (substr($node, -1) != '>') {
1251 0 : var_dump($this->_tokens);exit;
1252 : require_once 'Erfurt/Sparql/ParserException.php';
1253 : throw new Erfurt_Sparql_ParserException('Unclosed IRI: ' . $node, -1, key($this->_tokens));
1254 : }
1255 0 : return $this->_parseNode($node);
1256 : } else {
1257 0 : require_once 'Erfurt/Sparql/ParserException.php';
1258 0 : throw new Erfurt_Sparql_ParserException(
1259 0 : '"' . $node . '" is neither a valid rdf- node nor a variable.',
1260 0 : -1,
1261 0 : key($this->_tokens)
1262 0 : );
1263 : }
1264 :
1265 48 : return $node;
1266 : }
1267 :
1268 : /**
1269 : * Parses order conditions of a query.
1270 : *
1271 : * @throws Erfurt_Sparql_ParserException
1272 : */
1273 : protected function _parseOrderCondition()
1274 : {
1275 25 : $valList = array();
1276 25 : $val = array();
1277 :
1278 25 : while (strtolower(current($this->_tokens)) !== 'limit' && strtolower(current($this->_tokens)) != false
1279 25 : && strtolower(current($this->_tokens)) !== 'offset') {
1280 :
1281 25 : switch (strtolower(current($this->_tokens))) {
1282 25 : case 'desc':
1283 5 : $this->_fastForward();
1284 5 : $this->_fastForward();
1285 :
1286 5 : if ($this->_varCheck(current($this->_tokens))) {
1287 4 : $val['val'] = current($this->_tokens);
1288 5 : } else if ($this->_iriCheck(current($this->_tokens)) || $this->_qnameCheck(current($this->_tokens)) ||
1289 1 : in_array(current($this->_tokens), $this->_sops)) {
1290 :
1291 1 : $fName = current($this->_tokens);
1292 :
1293 : do {
1294 1 : $this->_fastForward();
1295 1 : $fName .= current($this->_tokens);
1296 1 : } while (current($this->_tokens) !== ')');
1297 :
1298 1 : $val['val'] = $fName;
1299 1 : } else {
1300 0 : require_once 'Erfurt/Sparql/ParserException.php';
1301 0 : throw new Erfurt_Sparql_ParserException('Variable expected in ORDER BY clause.', -1,
1302 0 : key($this->_tokens));
1303 : }
1304 :
1305 5 : $this->_fastForward();
1306 :
1307 5 : if (current($this->_tokens) != ')') {
1308 0 : require_once 'Erfurt/Sparql/ParserException.php';
1309 0 : throw new Erfurt_Sparql_ParserException('missing ")" in ORDER BY clause.', -1,
1310 0 : key($this->_tokens));
1311 : }
1312 :
1313 5 : $val['type'] = 'desc';
1314 5 : $this->_fastForward();
1315 5 : break;
1316 22 : case 'asc':
1317 10 : $this->_fastForward();
1318 10 : $this->_fastForward();
1319 :
1320 10 : if ($this->_varCheck(current($this->_tokens))) {
1321 10 : $val['val'] = current($this->_tokens);
1322 10 : } else if ($this->_iriCheck(current($this->_tokens)) || $this->_qnameCheck(current($this->_tokens)) ||
1323 0 : in_array(current($this->_tokens), $this->_sops)) {
1324 :
1325 0 : $fName = current($this->_tokens);
1326 :
1327 : do {
1328 0 : $this->_fastForward();
1329 0 : $fName .= current($this->_tokens);
1330 0 : } while (current($this->_tokens) !== ')');
1331 :
1332 0 : $val['val'] = $fName;
1333 0 : } else {
1334 0 : require_once 'Erfurt/Sparql/ParserException.php';
1335 0 : throw new Erfurt_Sparql_ParserException('Variable expected in ORDER BY clause. ', -1,
1336 0 : key($this->_tokens));
1337 : }
1338 :
1339 10 : $this->_fastForward();
1340 :
1341 10 : if (current($this->_tokens) !== ')') {
1342 0 : require_once 'Erfurt/Sparql/ParserException.php';
1343 0 : throw new Erfurt_Sparql_ParserException('missing ")" in ORDER BY clause.', -1,
1344 0 : key($this->_tokens));
1345 : }
1346 :
1347 10 : $val['type'] = 'asc';
1348 10 : $this->_fastForward();
1349 10 : break;
1350 13 : case ')':
1351 1 : $this->_fastForward();
1352 1 : break;
1353 13 : case '(':
1354 1 : $this->_fastForward();
1355 13 : default:
1356 13 : if ($this->_varCheck(current($this->_tokens))) {
1357 11 : $val['val'] = current($this->_tokens);
1358 11 : $val['type'] = 'asc';
1359 13 : } else if ($this->_iriCheck(current($this->_tokens)) || $this->_qnameCheck(current($this->_tokens)) ||
1360 2 : in_array(current($this->_tokens), self::$_sops)) {
1361 :
1362 2 : $fName = current($this->_tokens);
1363 :
1364 : do {
1365 2 : $this->_fastForward();
1366 2 : $fName .= current($this->_tokens);
1367 2 : } while (current($this->_tokens) !== ')');
1368 :
1369 2 : $val['val'] = $fName;
1370 2 : } else {
1371 0 : require_once 'Erfurt/Sparql/ParserException.php';
1372 : //TODO: fix recognition of "ORDER BY ASC(?x)"
1373 : //throw new Erfurt_Sparql_ParserException('Variable expected in ORDER BY clause.', -1,
1374 : // key($this->_tokens));
1375 : }
1376 :
1377 13 : $this->_fastForward();
1378 13 : break;
1379 25 : }
1380 25 : $valList[] = $val;
1381 25 : }
1382 25 : prev($this->_tokens);
1383 25 : $this->_query->setSolutionModifier('order by', $valList);
1384 25 : }
1385 :
1386 : /**
1387 : * Adds a new namespace prefix to the query object.
1388 : *
1389 : * @throws Erfurt_Sparql_ParserException
1390 : */
1391 : protected function _parsePrefix()
1392 : {
1393 201 : $this->_fastForward();
1394 201 : $prefix = substr(current($this->_tokens), 0, -1);
1395 201 : $this->_fastForward();
1396 201 : if ($this->_iriCheck(current($this->_tokens))) {
1397 201 : $uri = substr(current($this->_tokens), 1, -1);
1398 201 : $this->_query->addPrefix($prefix, $uri);
1399 201 : } else {
1400 0 : require_once 'Erfurt/Sparql/ParserException.php';
1401 0 : throw new Erfurt_Sparql_ParserException('IRI expected', -1, key($this->_tokens));
1402 : }
1403 201 : }
1404 :
1405 : /** Starts parsing the tokenized SPARQL Query. */
1406 : protected function _parseQuery()
1407 : {
1408 : do {
1409 334 : switch (strtolower(current($this->_tokens))) {
1410 334 : case 'base':
1411 28 : $this->_parseBase();
1412 28 : break;
1413 334 : case 'prefix':
1414 201 : $this->_parsePrefix();
1415 201 : break;
1416 334 : case 'select':
1417 316 : $this->_parseSelect();
1418 316 : break;
1419 334 : case 'describe':
1420 2 : $this->_parseDescribe();
1421 2 : break;
1422 333 : case 'ask':
1423 5 : $this->_parseAsk('ask');
1424 5 : break;
1425 333 : case 'count':
1426 2 : $this->_parseAsk('count');
1427 2 : break;
1428 333 : case 'count-distinct':
1429 0 : $this->_parseAsk('count-distinct');
1430 0 : break;
1431 333 : case 'from':
1432 25 : $this->_parseFrom();
1433 25 : break;
1434 333 : case 'construct':
1435 9 : $this->_parseConstruct();
1436 9 : break;
1437 324 : case 'where':
1438 275 : $this->_parseWhere();
1439 275 : $this->_parseModifier();
1440 275 : break;
1441 49 : case '{':
1442 49 : prev($this->_tokens);
1443 49 : $this->_parseWhere();
1444 49 : $this->_parseModifier();
1445 49 : break;
1446 334 : }
1447 334 : } while (next($this->_tokens));
1448 334 : }
1449 :
1450 : /**
1451 : * Parses the SELECT part of a query.
1452 : *
1453 : * @throws Erfurt_Sparql_ParserException
1454 : */
1455 : protected function _parseSelect()
1456 : {
1457 316 : $this->_fastForward();
1458 316 : $curLow = strtolower(current($this->_tokens));
1459 316 : prev($this->_tokens);
1460 316 : if ($curLow === 'distinct') {
1461 11 : $this->_query->setResultForm('select distinct');
1462 11 : } else {
1463 305 : $this->_query->setResultForm('select');
1464 : }
1465 :
1466 316 : $currentVar = null;
1467 316 : $currentFunc = null;
1468 316 : $bWaitForRenaming = false;
1469 316 : while ($curLow != 'from' && $curLow != 'where' && $curLow != "{") {
1470 316 : $this->_fastForward();
1471 316 : $curTok = current($this->_tokens);
1472 316 : $curLow = strtolower($curTok);
1473 :
1474 316 : if ($this->_varCheck($curTok) || $curLow == '*') {
1475 316 : if ($bWaitForRenaming) {
1476 0 : $bWaitForRenaming = false;
1477 0 : $currentVar->setAlias($curTok);
1478 0 : if ($currentFunc != null) {
1479 0 : $currentVar->setFunc($currentFunc);
1480 0 : }
1481 0 : $this->_query->addResultVar($currentVar);
1482 0 : $currentVar = null;
1483 0 : } else {
1484 316 : if ($currentVar != null) {
1485 70 : $this->_query->addResultVar($currentVar);
1486 70 : $currentVar = null;
1487 70 : }
1488 316 : require_once 'Erfurt/Sparql/QueryResultVariable.php';
1489 316 : $currentVar = new Erfurt_Sparql_QueryResultVariable($curTok);
1490 316 : if ($currentFunc != null) {
1491 0 : $currentVar->setFunc($currentFunc);
1492 0 : }
1493 : }
1494 316 : $currentFunc = null;
1495 316 : } else if ($curLow == 'as') {
1496 0 : if ($currentVar === null) {
1497 0 : require_once 'Erfurt/Sparql/ParserException.php';
1498 0 : throw new Erfurt_Sparql_ParserException('AS requires a variable left and right', -1,
1499 0 : key($this->_tokens));
1500 : }
1501 0 : $bWaitForRenaming = true;
1502 316 : } else if (in_array($curLow, self::$_sops)) {
1503 0 : $currentFunc = $curLow;
1504 0 : }
1505 :
1506 316 : if (!current($this->_tokens)) {
1507 0 : require_once 'Erfurt/Sparql/ParserException.php';
1508 0 : throw new Erfurt_Sparql_ParserException(
1509 0 : 'Unexpected end of query.', -1, key($this->_tokens));
1510 : }
1511 316 : }
1512 :
1513 316 : if ($currentVar != null) {
1514 316 : $this->_query->addResultVar($currentVar);
1515 316 : }
1516 316 : prev($this->_tokens);
1517 :
1518 316 : if (count($this->_query->getResultVars()) == 0) {
1519 0 : require_once 'Erfurt/Sparql/ParserException.php';
1520 0 : throw new Erfurt_Sparql_ParserException('Variable or "*" expected.', -1, key($this->_tokens));
1521 : }
1522 316 : }
1523 :
1524 : /**
1525 : * Parses a triple pattern.
1526 : *
1527 : * @param Sparql_GraphPattern $pattern
1528 : */
1529 : protected function _parseTriplePattern(&$pattern)
1530 : {
1531 314 : $trp = array();
1532 314 : $prev = false;
1533 314 : $prevPred = false;
1534 314 : $cont = true;
1535 314 : $needsDot = false;
1536 314 : $dotAllowed = true;
1537 314 : $sub = '';
1538 314 : $pre = '';
1539 314 : $tmp = '';
1540 314 : $tmpPred = '';
1541 314 : $obj = '';
1542 :
1543 : do {
1544 314 : switch (strtolower(current($this->_tokens))) {
1545 314 : case false:
1546 0 : $cont = false;
1547 0 : $pattern->open = false;
1548 0 : break;
1549 314 : case 'filter':
1550 84 : $this->_parseConstraint($pattern, false);
1551 :
1552 84 : if (strtolower(current($this->_tokens)) !== 'filter' &&
1553 84 : strtolower(current($this->_tokens)) !== 'optional') {
1554 :
1555 82 : $this->_fastForward();
1556 82 : }
1557 :
1558 84 : $needsDot = false;
1559 84 : break;
1560 314 : case 'optional':
1561 33 : $needsDot = false;
1562 33 : $this->_fastForward();
1563 33 : $this->_parseGraphPattern($pattern->getId(), false);
1564 33 : break;
1565 314 : case 'union':
1566 2 : $this->_fastForward();
1567 2 : $this->_parseGraphPattern(false, $this->_tmp, false, false, false, $pattern->getId());
1568 2 : break;
1569 314 : case ';':
1570 : // Check whether the previous token is a dot too, for this is not allowed.
1571 24 : $this->_rewind();
1572 24 : if (current($this->_tokens) === '.') {
1573 0 : require_once 'Erfurt/Sparql/ParserException.php';
1574 0 : throw new Erfurt_Sparql_ParserException('A semicolon must not follow a dot directly.', -1,
1575 0 : key($this->_tokens));
1576 : }
1577 24 : $this->_fastForward();
1578 :
1579 24 : $prev = true;
1580 24 : $needsDot = false;
1581 24 : $this->_fastForward();
1582 24 : break;
1583 314 : case '.':
1584 158 : if ($dotAllowed === false) {
1585 0 : require_once 'Erfurt/Sparql/ParserException.php';
1586 0 : throw new Erfurt_Sparql_ParserException('A dot is not allowed here.', -1, key($this->_tokens));
1587 : }
1588 :
1589 : // Check whether the previous token is a dot too, for this is not allowed.
1590 158 : $this->_rewind();
1591 158 : if (current($this->_tokens) === '.') {
1592 0 : require_once 'Erfurt/Sparql/ParserException.php';
1593 0 : throw new Erfurt_Sparql_ParserException('A dot may not follow a dot directly.', -1,
1594 0 : key($this->_tokens));
1595 : }
1596 158 : $this->_fastForward();
1597 :
1598 158 : $prev = false;
1599 158 : $needsDot = false;
1600 158 : $this->_fastForward();
1601 158 : break;
1602 314 : case 'graph':
1603 3 : $this->_parseGraph();
1604 3 : break;
1605 314 : case ',':
1606 0 : require_once 'Erfurt/Sparql/ParserException.php';
1607 0 : throw new Erfurt_Sparql_ParserException('A comma is not allowed directly after a triple.', -1,
1608 0 : key($this->_tokens));
1609 :
1610 : $prev = true;
1611 : $prevPred = true;
1612 : $this->_fastForward();
1613 : break;
1614 314 : case '}':
1615 314 : $prev = false;
1616 314 : $pattern->open = false;
1617 314 : $cont = false;
1618 314 : $this->_dissallowBlankNodes();
1619 314 : break;
1620 314 : case '{':
1621 : //subpatterns opens
1622 2 : $this->_parseGraphPattern(false, false, false, false, false, $pattern->getId());
1623 2 : $needsDot = false;
1624 2 : break;
1625 314 : case "[":
1626 10 : $needsDot = false;
1627 10 : $prev = true;
1628 10 : $tmp = $this->_parseNode($this->_query->getBlanknodeLabel());
1629 10 : $this->_fastForward();
1630 10 : break;
1631 314 : case "]":
1632 10 : $needsDot = false;
1633 10 : $dotAllowed = false;
1634 10 : $prev = true;
1635 10 : $this->_fastForward();
1636 10 : break;
1637 314 : case "(":
1638 11 : $prev = true;
1639 11 : $tmp = $this->_parseCollection($trp);
1640 11 : $this->_fastForward();
1641 11 : break;
1642 309 : case false:
1643 0 : $cont = false;
1644 0 : $pattern->open = false;
1645 0 : break;
1646 309 : default:
1647 309 : if ($needsDot === true) {
1648 0 : require_once 'Erfurt/Sparql/ParserException.php';
1649 0 : throw new Erfurt_Sparql_ParserException('Two triple pattern need to be seperated by a dot. In Query: '.htmlentities($this->_query), -1,
1650 0 : key($this->_tokens));
1651 : }
1652 :
1653 309 : $dotAllowed = false;
1654 :
1655 309 : if ($prev) {
1656 33 : $sub = $tmp;
1657 33 : } else {
1658 296 : $sub = $this->_parseNode();
1659 296 : $this->_fastForward();
1660 296 : $tmp = $sub;
1661 : }
1662 309 : if ($prevPred) {
1663 0 : $pre = $tmpPred;
1664 0 : } else {
1665 : // Predicates may not be blank nodes.
1666 309 : if ((current($this->_tokens) === '[') || (substr(current($this->_tokens), 0, 2) === '_:')) {
1667 0 : require_once 'Erfurt/Sparql/ParserException.php';
1668 0 : throw new Erfurt_Sparql_ParserException('Predicates may not be blank nodes.', -1,
1669 0 : key($this->_tokens));
1670 : }
1671 :
1672 309 : $pre = $this->_parseNode();
1673 309 : $this->_fastForward();
1674 309 : $tmpPred = $pre;
1675 : }
1676 :
1677 309 : if (current($this->_tokens) === '[') {
1678 3 : $tmp = $this->_parseNode($this->_query->getBlanknodeLabel());
1679 3 : $prev = true;
1680 3 : $obj = $tmp;
1681 :
1682 3 : require_once 'Erfurt/Sparql/QueryTriple.php';
1683 3 : $trp[] = new Erfurt_Sparql_QueryTriple($sub, $pre, $obj);
1684 3 : $dotAllowed = true;
1685 3 : $this->_fastForward();
1686 3 : continue;
1687 307 : } else if (current($this->_tokens) === '(') {
1688 2 : $obj = $this->_parseCollection($trp);
1689 2 : } else {
1690 305 : $obj = $this->_parseNode();
1691 : }
1692 :
1693 307 : require_once 'Erfurt/Sparql/QueryTriple.php';
1694 307 : $trp[] = new Erfurt_Sparql_QueryTriple($sub, $pre, $obj);
1695 307 : $dotAllowed = true;
1696 307 : $needsDot = true;
1697 307 : $this->_fastForward();
1698 307 : break;
1699 314 : }
1700 314 : } while ($cont);
1701 :
1702 314 : if (count($trp) > 0) {
1703 314 : $pattern->addTriplePatterns($trp);
1704 314 : }
1705 314 : }
1706 :
1707 : /**
1708 : * Parses the WHERE clause.
1709 : *
1710 : * @throws Erfurt_Sparql_ParserException
1711 : */
1712 : protected function _parseWhere()
1713 : {
1714 333 : $this->_fastForward();
1715 :
1716 333 : if (current($this->_tokens) === '{') {
1717 333 : $this->_parseGraphPattern();
1718 333 : } else {
1719 0 : require_once 'Erfurt/Sparql/ParserException.php';
1720 0 : throw new Erfurt_Sparql_ParserException('Unable to parse WHERE part. "{" expected in Query. ', -1,
1721 0 : key($this->_tokens));
1722 : }
1723 333 : }
1724 :
1725 : /**
1726 : * Set all internal variables to a clear state
1727 : * before we start parsing.
1728 : */
1729 : protected function _prepare()
1730 : {
1731 334 : require_once 'Erfurt/Sparql/Query.php';
1732 334 : $this->_query = new Erfurt_Sparql_Query();
1733 :
1734 334 : $this->_tokens = array();
1735 334 : $this->_tmp = null;
1736 334 : }
1737 :
1738 : /**
1739 : * Checks if $token is a qname.
1740 : *
1741 : * @param string $token The token
1742 : * @return boolean true if the token is a qname false if not
1743 : * @throws Erfurt_Sparql_ParserException
1744 : */
1745 : protected function _qnameCheck($token)
1746 : {
1747 304 : $pattern = "/^([^:^\<]*):([^:]*)$/";
1748 :
1749 304 : if (preg_match($pattern, $token, $hits) > 0) {
1750 167 : $prefs = $this->_query->getPrefixes();
1751 167 : if (isset($prefs[$hits{1}])) {
1752 167 : return true;
1753 : }
1754 0 : if ($hits{1} === '_') {
1755 0 : return true;
1756 : }
1757 :
1758 0 : require_once 'Erfurt/Sparql/ParserException.php';
1759 0 : throw new Erfurt_Sparql_ParserException('Unbound Prefix: <i>' . $hits{1} . '</i>', -1, key($this->_tokens));
1760 : } else {
1761 269 : return false;
1762 : }
1763 : }
1764 :
1765 : /** Rewind until next token which is not blank. */
1766 : protected function _rewind()
1767 : {
1768 176 : prev($this->_tokens);
1769 176 : }
1770 :
1771 : /**
1772 : * Checks if $token is a variable.
1773 : *
1774 : * @param string $token The token
1775 : * @return boolean true if the token is a variable false if not
1776 : */
1777 : protected function _varCheck($token)
1778 : {
1779 332 : if (isset($token[0]) && ($token{0} == '$' || $token{0} == '?')) {
1780 250 : $this->_query->addUsedVar($token);
1781 250 : return true;
1782 : }
1783 :
1784 319 : return false;
1785 : }
1786 : }
|